package org.tulkas.robot;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.AbstractContentBody;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tulkas.robot.exception.SystemException;
import org.tulkas.robot.http.ClientOptions;
import org.tulkas.robot.http.headers.RequestHeaders;
import org.tulkas.util.HttpUtil;

/**
 * @author <a href="mailto:tulkas.web@gmail.com">Tulkas</a>
 *
 */
public class Form {
	private static Logger logger = LoggerFactory.getLogger(Form.class);

	public static final String FORM_METHOD_POST = "post";
	public static final String FORM_METHOD_GET = "get";

	private String action = "";

	private String method = FORM_METHOD_POST;

	private String enctype = RequestHeaders.MEDIA_TYPEs.FORM;

	private String charset;

	public final List<FormItem> values = new ArrayList<FormItem>();

	public Form() {
	}

	public Form(String action) {
		this.action = action;
	}

	public String getAction() {
		return action;
	}

	public void setAction(String action) {
		this.action = action;
	}

	public String getEnctype() {
		return enctype;
	}

	public void setEnctype(String enctype) {
		this.enctype = enctype;
	}

	public String getCharset() {
		return charset;
	}

	public void setCharset(String charset) {
		this.charset = charset;
	}

	public String getMethod() {
		return method;
	}

	public void setMethod(String method) {
		this.method = method;
	}

	public void addValue(String key, String value) {
		values.add(new FormItem(key, value));
	}

	public void setValue(String key, String value) {
		remove(key);
		addValue(key, value);
	}

	public void addValue(String key, File file) {
		values.add(new FormItem(key, file));
	}

	public void setValue(String key, File file) {
		remove(key);
		addValue(key, file);
	}

	public void addValue(String key, InputStream is, String mimeType,
			String fileName) {
		values.add(new FormItem(key,
				new InputStreamBody(is, mimeType, fileName)));
	}

	public void setValue(String key, InputStream is, String mimeType,
			String fileName) {
		remove(key);
		addValue(key, is, mimeType, fileName);
	}

	public void addValue(String key, byte[] bytes, String mimeType,
			String fileName) {
		values.add(new FormItem(key, new ByteArrayBody(bytes, mimeType,
				fileName)));
	}

	public void setValue(String key, byte[] bytes, String mimeType,
			String fileName) {
		remove(key);
		addValue(key, bytes, mimeType, fileName);
	}

	public void remove(String key) {
		if (key == null)
			return;
		Iterator<FormItem> it = values.iterator();
		while (it.hasNext()) {
			FormItem fi = it.next();
			if (key.equals(fi.key))
				it.remove();
		}
	}

	public void clear() {
		values.clear();
	}

	public Iterator<FormItem> iterateItems() {
		return values.iterator();
	}

	public Object getContent(String key) {
		if (key == null)
			return null;
		List<Object> list = new ArrayList<Object>();
		for (FormItem fi : values) {
			if (key.equals(fi.key)) {
				list.add(fi.content);
			}
		}
		if (list.size() == 0)
			return null;
		else if (list.size() == 1)
			return list.get(0);
		else {
			return list.toArray();
		}
	}

	protected HttpRequestBase prepareRequestForBasicForm(Page newPage)
			throws IOException {
		List<NameValuePair> nvps = new ArrayList<NameValuePair>();
		Iterator<FormItem> it = this.iterateItems();
		while (it.hasNext()) {
			FormItem fi = it.next();
			String key = fi.getKey();
			Object value = fi.getContent();
			if (value instanceof String) {
				NameValuePair nvp = new BasicNameValuePair(key, (String) fi
						.getContent());
				nvps.add(nvp);
			} else {
				logger.error("Illegal type of form data [" + key
						+ "] in the form submitted to [" + this.getAction()
						+ "], ignore the data");
			}
		}

		StringEntity entity = new UrlEncodedFormEntity(nvps, this.getCharset());
		if (Form.FORM_METHOD_GET.equalsIgnoreCase(this.getMethod())) {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			entity.writeTo(bos);
			byte[] bytes = bos.toByteArray();
			bos.close();
			String newUrl = HttpUtil.attachParametersToUrl(newPage.getUrl(),
					new String(bytes));
			newPage.setUrl(newUrl);
			newPage.getRequestHeaders().setContentType(
					RequestHeaders.MEDIA_TYPEs.FORM + " charset="
							+ this.getCharset());
			return new HttpGet(newUrl);
		} else {
			HttpPost post = new HttpPost(newPage.getUrl());
			post.setEntity(entity);
			return post;
		}
	}

	protected HttpRequestBase prepareRequestForMultipartForm(Page newPage)
			throws IOException {
		MultipartEntity reqEntity = new MultipartEntity();
		Iterator<FormItem> it = this.iterateItems();
		while (it.hasNext()) {
			FormItem fi = it.next();
			String key = fi.getKey();
			Object value = fi.getContent();
			if (value instanceof String) {
				StringBody body = new StringBody((String) value);
				reqEntity.addPart(key, body);
			} else if (value instanceof File) {
				FileBody body = new FileBody((File) value);
				reqEntity.addPart(key, body);
			} else if (value instanceof AbstractContentBody) {
				reqEntity.addPart(key, (AbstractContentBody) value);
			}
		}

		HttpPost post = new HttpPost(newPage.getUrl());
		post.setEntity(reqEntity);
		return post;
	}

	public void normalize(Page from, ClientOptions options) {
		Iterator<FormItem> it = iterateItems();
		while (it.hasNext()) {
			FormItem fi = it.next();
			if (fi.getContent() instanceof File
					|| fi.getContent() instanceof InputStream) {
				enctype = RequestHeaders.MEDIA_TYPEs.FORM_DATA;
				method = Form.FORM_METHOD_POST;
			}
		}

		if (charset == null && from != null)
			charset = from.getCharset();
		if (charset == null)
			charset = options.getDefaultCharset();
	}

	public HttpRequestBase createHttpRequest(Page newPage) throws IOException {
		String enc = this.getEnctype();
		HttpRequestBase ret = null;
		if (RequestHeaders.MEDIA_TYPEs.FORM.equals(enc)) {
			ret = prepareRequestForBasicForm(newPage);
		} else if (RequestHeaders.MEDIA_TYPEs.FORM_DATA.equals(enc)) {
			ret = prepareRequestForMultipartForm(newPage);
		} else {
			throw new SystemException("Unknown form enctype: ["
					+ this.getEnctype() + "]");
		}

		return ret;
	}

	public static class FormItem {
		FormItem(String key, Object content) {
			this.key = key;
			this.content = content;
		}

		private String key;

		private Object content;

		public String getKey() {
			return key;
		}

		public Object getContent() {
			return content;
		}
	}
}
